package com.ruoyi.central.service.custom.impl;

import com.ruoyi.central.controller.vo.ProcessStepRunVo;
import com.ruoyi.central.domain.*;
import com.ruoyi.central.enums.MaterialStatusEnum;
import com.ruoyi.central.enums.OperationTypeEnum;
import com.ruoyi.central.service.*;
import com.ruoyi.central.service.custom.IManualOperationStationService;
import com.ruoyi.central.utils.pojo.MaterialUtils;
import com.ruoyi.central.utils.pojo.ProcessStepUtils;
import com.ruoyi.common.enums.common.TaskStatusEnum;
import com.ruoyi.common.exception.base.service.ServiceException;
import com.ruoyi.common.utils.CollectionUtils;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.bean.EnumUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Classname ManualOperationStationServiceImpl
 * @Author: sunluquan
 * @Date: 2022/6/14 18:22
 * @Description TODO
 * 操作工位页面相关业务对象
 */
@Service
@Slf4j
public class ManualOperationStationServiceImpl implements IManualOperationStationService {

    @Autowired
    private ITopCentralOperationStationMaterialService operationStationMaterialService;

    @Autowired
    private ITopCentralMaterialService materialService;

    @Autowired
    private ITopCentralArtProcessRunService artProcessRunService;

    @Autowired
    private ITopCentralTaskCmdRunService taskCmdRunService;

    @Autowired
    private ITopCentralProcessStepRunService topCentralProcessStepRunService;

    @Autowired
    private ITopCentralArtProcessService artProcessService;

    @Autowired
    private ITopCentralProcessStepService processStepService;

    @Autowired
    private ITopCentralProcessStepRunService processStepRunService;

    @Autowired
    private ITopCentralTaskCmdService taskCmdService;

    /**
     * 完成工序执行
     *  1.修改当前执行工序为完成
     *  2.如果当前执行工序不是最后一道工序 进入 2.1 否则进入2.2
     *
     *  2.1修改下一道工序为开始执行
     *  2.1-1判断下一道工序是否有执行指令，且执行指令第一个操作类型为 手动 就进入 2.1-2
     *  1.2-2修改第一道执行指令为开始执行
     *
     *  2.2修改当前工序的工艺执行对象 为完成
     *  2.2.1删除 物料-操作工位绑定对象
     *  2.2.2修改物料对象的 物料状态为 已加工完成
     *  2.2.3判断当前完成物料是否可以创建父物料
     * @param processStepRunVo 活跃得工序执行对象
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public TopCentralArtProcessRun toCompleteProcess(ProcessStepRunVo processStepRunVo) {

        //当前执行工序的工艺对象
        TopCentralArtProcessRun activeArtProcessRun = fillAndCheckForToCompleteProcess(processStepRunVo);
        //当前执行工艺下所有的执行工序对象
        List<TopCentralProcessStepRun> processStepRuns = activeArtProcessRun.getProcessStepRuns();
        //当前执行工艺下需要完成的执行工序对象
        TopCentralProcessStepRun activeProcessStepRun = activeArtProcessRun.getProcessStepRun();
        //当前需要修改的执行工序对象 集合容器
        List<TopCentralProcessStepRun> processStepRunsOfUpdate = new ArrayList<>(2);
        //当前加工工序的物料
        TopCentralMaterial material = activeArtProcessRun.getMaterial();
        String materialId = material.getId();

        activeProcessStepRun.setTaskStatus(TaskStatusEnum.EXECUTED_FINISH.getValue());

        processStepRunsOfUpdate.add(BeanUtils.initialize(TopCentralProcessStepRun.class,finishedProcessStepRun->{
            //当前需要完成的执行工序对象编号
            finishedProcessStepRun.setId(activeProcessStepRun.getId());
            finishedProcessStepRun.setTaskStatus(activeProcessStepRun.getTaskStatus());
            finishedProcessStepRun.setRemark(processStepRunVo.getRemark());
        }));

        if(processStepRuns.size()>1){
            //修改下一道工序为执行
            TopCentralProcessStepRun nextProcessStepRun = processStepRuns.get(1);
            Integer nextProcessStepRunId = nextProcessStepRun.getId();
            nextProcessStepRun.setTaskStatus(TaskStatusEnum.EXECUTING.getValue());
            //创建时间 表示当前工序的开始执行时间
            nextProcessStepRun.setCreateTime(DateUtils.getNowDate());

            processStepRunsOfUpdate.add(BeanUtils.initialize(TopCentralProcessStepRun.class,activeProcessStepRunOfUpdate->{
                activeProcessStepRunOfUpdate.setId(nextProcessStepRunId);
                activeProcessStepRunOfUpdate.setTaskStatus(nextProcessStepRun.getTaskStatus());
            }));

            //TODO 生成当前工序下所有的执行指令 并将第一个设置为执行中
            List<TopCentralTaskCmdRun> taskCmdRuns = taskCmdRunService.selectByProcessStepRunId(nextProcessStepRunId);
            if(StringUtils.isNotEmpty(taskCmdRuns)){
                if(taskCmdRuns.size()>1){
                    taskCmdRuns.sort(Comparator.comparing(TopCentralTaskCmdRun::getWeight));
                }
                TopCentralTaskCmdRun taskCmdRunOfFirst = CollectionUtils.getFirst(taskCmdRuns);

                TopCentralTaskCmd taskCmd = taskCmdRunOfFirst.getTaskCmd();
                Assert.notNull(taskCmd,"未找到任务执行指令！");
                if(EnumUtils.equals(OperationTypeEnum.MANUAL_TYPE,taskCmd.getCommandType())){
                    TopCentralTaskCmdRun taskCmdRunOfActive =
                            BeanUtils.initialize(TopCentralTaskCmdRun.class,(taskCmdRun -> {
                                taskCmdRun.setId(taskCmdRunOfFirst.getId());
                                taskCmdRun.setTaskStatus(TaskStatusEnum.NOT_START.getValue());
                            }));
                    taskCmdRunService.updateById(taskCmdRunOfActive);
                }
            }
        }else{//如果只有一条数据 说明当前执行工序是最后一道执行工序 //需要进行特殊操作
            //如果是最后一道工序 修改当前执行工艺为完成
            activeArtProcessRun.setTaskStatus(TaskStatusEnum.EXECUTED_FINISH);
            //操作工位-物料 绑定表的行数据编号
            Integer operationStationMaterialId = null;
            //操作工位-物料对象 （当前工序为当前工艺的最后一条完成工序后 需要将绑定数据删除）
            TopCentralOperationStationMaterial operationStationMaterial = null;

            TopCentralArtProcessRun updateArtProcessRunForComplete = new TopCentralArtProcessRun();
            updateArtProcessRunForComplete.setId(activeArtProcessRun.getId());
            updateArtProcessRunForComplete.setTaskStatus(activeArtProcessRun.getTaskStatus());

            //TODO 需要将当前物料与操作台 解绑
            operationStationMaterial = operationStationMaterialService.selectByMaterialId(materialId);

            Assert.notNull(operationStationMaterial,"物料编号："+materialId+" ,绑定的操作工位丢失！");

            operationStationMaterialId = operationStationMaterial.getId();

            //TODO 需要修改物料表的状态
            TopCentralMaterial materialOfUpdate = new TopCentralMaterial();
            materialOfUpdate.setOperationType(Boolean.TRUE);
            materialOfUpdate.setOldMaterial(material);
            materialOfUpdate.setId(materialId);
            materialOfUpdate.setMaterialStatus(MaterialStatusEnum.FINISHED_OF_STATUS.getValue());


            artProcessRunService.updateById(updateArtProcessRunForComplete);
            operationStationMaterialService.deleteById(operationStationMaterialId);
            materialService.publishAndUpdateById(materialOfUpdate);

        }
        if(processStepRunsOfUpdate.size()==1){
            topCentralProcessStepRunService.updateById(CollectionUtils.getFirst(processStepRunsOfUpdate));
        }else{
            topCentralProcessStepRunService.updateBatchById(processStepRunsOfUpdate);
        }

        return activeArtProcessRun;
    }

    /**
     * 初始化物料与操作工位的关系 以及 初始化 执行工艺、执行工序、执行指令。
     * 1.修改物料状态为 使用中
     * 2.新增 物料-操作工位绑定对象
     * 3.新增 执行工艺对象（默认为执行状态）
     * 4.新增 执行工序对象（默认第一个执行工序为执行中状态）
     * 5.新增 执行指令对象（如果第一道工序的第一条指令为手动类型，将其状态修改为执行中状态）
     * @param operationStationMaterial
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void checkAndInit(TopCentralOperationStationMaterial operationStationMaterial) {
        TopCentralMaterial material = fillAndConformMaterialInitialization(operationStationMaterial);

        String materialNo = material.getId();
        Integer operationStationId = material.getOperationStationId();
        //需要修改的物料对象
        TopCentralMaterial materialOfUpdate = null;
        //需要新增的 操作工位-物料 绑定 对象
        TopCentralOperationStationMaterial operationStationMaterialOfInsert = null;
        //物料绑定的工艺对象
        TopCentralArtProcess artProcess = material.getArtProcess();
        //新增的工艺执行对象（跟物料编号、工艺对象编号绑定）
        TopCentralArtProcessRun topCentralArtProcessRunOfInsert = null;
        //工艺下的工序列表
        List<TopCentralProcessStep> processSteps = artProcess.getProcessSteps();

        //1

        materialOfUpdate = new TopCentralMaterial();
        materialOfUpdate.setId(materialNo);
        materialOfUpdate.setOperationType(Boolean.TRUE);
        materialOfUpdate.setOldMaterial(material);
        materialOfUpdate.setMaterialStatus(MaterialStatusEnum.USE_OF_STATUS.getValue());
        materialService.updateById(materialOfUpdate);

        //2

        operationStationMaterialOfInsert = new TopCentralOperationStationMaterial(materialNo,operationStationId);

        try {
            //可能出现违反主键 或 唯一键 约束
            operationStationMaterialService.insert(operationStationMaterialOfInsert);
        } catch (Exception e) {
            e.printStackTrace();
            if(e instanceof DuplicateKeyException){
                throw new ServiceException("操作工位-物料绑定失败，操作工位或物料已有绑定关系！");
            }else{
                throw new ServiceException("操作工位-物料绑定失败！");
            }
        }

        //3

        topCentralArtProcessRunOfInsert = TopCentralArtProcessRun.build(artProcess,material);
        topCentralArtProcessRunOfInsert.setTaskStatus(TaskStatusEnum.EXECUTING);
        artProcessRunService.insert(topCentralArtProcessRunOfInsert);
        material.setArtProcessRun(topCentralArtProcessRunOfInsert);

        //4

        List<TopCentralProcessStepRun> processStepRunsOfInsert = new ArrayList<>();
        processSteps.forEach(item->{
            TopCentralProcessStepRun processStepRunOfInsert = TopCentralProcessStepRun.build(item, material.getArtProcessRun());
            //下面会用到
            processStepRunOfInsert.setProcessStep(item);
            processStepRunsOfInsert.add(processStepRunOfInsert);
        });
        processStepRunsOfInsert.sort(Comparator.comparing(TopCentralProcessStepRun::getWeight));
        //工艺下的第一个工序需要修改任务状态为开始
        TopCentralProcessStepRun firstProcessStepRun = CollectionUtils.getFirst(processStepRunsOfInsert);
        firstProcessStepRun.setTaskStatus(TaskStatusEnum.EXECUTING.getValue());
        processStepRunService.insertBatch(processStepRunsOfInsert);

        //5

        //k 执行工序编号 v执行任务集合
        List<TopCentralTaskCmdRun> taskCmdRunsOfInsert = new ArrayList<>();
        //循环执行工序，取出每一个执行工序中的
        processStepRunsOfInsert.forEach(processStepRunOfInsert->{
            TopCentralProcessStep processStep = processStepRunOfInsert.getProcessStep();
            List<TopCentralTaskCmd> taskCmdList = processStep.getTaskCmdList();
            if(StringUtils.isNotEmpty(taskCmdList)){
                taskCmdList.forEach(taskCmd -> taskCmdRunsOfInsert.add(TopCentralTaskCmdRun.build(taskCmd,processStepRunOfInsert)));
            }
        });
        if(StringUtils.isNotEmpty(taskCmdRunsOfInsert)){
            //如果第一道工序的第一条执行指令是自动的 需要将任务未开始状态 修改为 任务执行状态
            TopCentralTaskCmdRun taskCmdRun = CollectionUtils.getFirst(taskCmdRunsOfInsert);
            if(EnumUtils.equals(OperationTypeEnum.MANUAL_TYPE,taskCmdRun.getTaskCmd().getType())){
                taskCmdRun.setTaskStatus(TaskStatusEnum.EXECUTING.getValue());
            }
            taskCmdRunService.insertBatch(taskCmdRunsOfInsert);
        }
    }

    /**
     * 检测工艺执行对象
     * 检测工序执行对象(工序执行对象获取的是当前工艺下顺序 任务状态为未完成的两个数据)
     * 检测物料对象
     * 将工序对象 和 物料对象 都填充指 工序执行对象中
     * 判断当前工序下的执行任务是否全部完成
     * @param processStepRunVo
     * @return 填充完毕的 工艺执行对象
     */
    private TopCentralArtProcessRun fillAndCheckForToCompleteProcess(ProcessStepRunVo processStepRunVo){

        Integer artProcessRunId = processStepRunVo.getArtProcessRunId();

        Integer processStepRunId = processStepRunVo.getProcessStepRunId();

        String remark = processStepRunVo.getRemark();

        TopCentralArtProcessRun activeArtProcessRun = null;
        //当前执行工艺下所有的执行工序对象
        List<TopCentralProcessStepRun> processStepRuns = null;
        //当前执行工艺下需要完成的执行工序对象
        TopCentralProcessStepRun activeProcessStepRun = null;
        //当前需要完成的执行工序对象编号
        Integer activeProcessStepRunId = null;
        String materialId = null;

        TopCentralMaterial material = null;

        activeArtProcessRun = artProcessRunService.selectUsableById(artProcessRunId);
        Assert.notNull(activeArtProcessRun,StringUtils.format("当前执行工艺编号：{}不存在或状态为不可用！",artProcessRunId));

        materialId = activeArtProcessRun.getProdId();

        material = materialService.selectById(materialId);
        Assert.notNull(material,StringUtils.format("当前物料编号：{}不存在或状态为不可用！",materialId));

        MaterialUtils.checkMaterialStatus(material,MaterialStatusEnum.USE_OF_STATUS);

        activeArtProcessRun.setMaterial(material);

        processStepRuns = topCentralProcessStepRunService.selectByArtProcessRunId(artProcessRunId);
        Assert.notEmpty(processStepRuns,StringUtils.format("当前执行工艺编号：{} 未找到绑定的可执行工序对象！",artProcessRunId));
        activeArtProcessRun.setProcessStepRuns(processStepRuns);

        activeProcessStepRun = CollectionUtils.getFirst(processStepRuns);

        if(!StringUtils.equals(activeProcessStepRun.getId(),processStepRunId)){
            TopCentralProcessStepRun processStepRun = topCentralProcessStepRunService.selectUsableById(processStepRunId);
            Assert.notNull(activeArtProcessRun,StringUtils.format("当前执行工序编号：{}不存在或状态为不可用!",processStepRunId));

            if(!StringUtils.equals(artProcessRunId,processStepRun.getArtProcessRunId())){
                throw new ServiceException("当前执行工序：{} 与当前执行工艺不符！",processStepRun.getId());
            }
            if(processStepRun.getWeight()>activeProcessStepRun.getWeight()){
                throw new ServiceException("当前执行工序：{} 之前还有未执行完成的工序，不能完成！",processStepRun.getId());
            }
            throw new ServiceException("当前执行工序：{} 与实际不符！",processStepRun.getId());
        }
        if(!EnumUtils.equals(TaskStatusEnum.EXECUTING,activeProcessStepRun.getTaskStatus())){
            throw new ServiceException("当前执行工序状态为：{}，不能完成！",EnumUtils.toEnum(TaskStatusEnum.class,activeProcessStepRun.getTaskStatus()));
        }

        activeProcessStepRunId = activeProcessStepRun.getId();

        //TODO 检查是否当前完成的工序下 所有的执行指令都完成了
        //如果当前需要修改为完成的工序 下面还有未执行完成的执行任务指令  抛出异常
        if(!taskCmdRunService.isTaskCmdRunFinishedByProcessStepRunId(activeProcessStepRunId)){
            throw new ServiceException("当前执行工序：{} ，还有未执行完成的执行任务指令，请先完成当前工序下所有的执行任务指令，再完成当前执行工序！",activeProcessStepRun.getName());
        }

        activeArtProcessRun.setProcessStepRun(activeProcessStepRun);

        return activeArtProcessRun;
    }
    /**
     * 1.检测物料是否符合初始化要求
     * 2.获取物料绑定的工艺对象（检测工艺对象）
     * 3.获取工艺对象下的工序集合（检测工序集合）
     * 4.获取工序集合下每一个工序的任务指令集合（检测任务指令集合  如果有的话）
     * 并将其填充至物料对象中
     * @param operationStationMaterial
     * @return 受信任的 物料对象
     */
    private TopCentralMaterial fillAndConformMaterialInitialization(TopCentralOperationStationMaterial operationStationMaterial){
        //操作工位编号
        Integer operationStationId = operationStationMaterial.getOperationStationId();
        //物料编号
        String materialNo = operationStationMaterial.getMaterialId();
        //物料绑定的工艺编号
        Integer artProcessId;
        //物料绑定的工艺对象
        TopCentralArtProcess artProcess = null;
        //当前工艺对象下的工序集合
        List<TopCentralProcessStep> processSteps = null;

        TopCentralMaterial material = materialService.selectUsableById(materialNo);

        Assert.notNull(material,StringUtils.format("当前物料编号：{}不存在或状态为不可用！",materialNo));

        MaterialUtils.checkMaterialStatus(material,MaterialStatusEnum.FREE_OF_STATUS);

        //如果当前物料的初始工位编号 与需要绑定的工位不一致
        if(!StringUtils.equals(material.getOperationStationId(),operationStationId)){
            throw new ServiceException("物料编号：{}，初始站位与当前站位不一致！",materialNo);
        }

        artProcessId = material.getArtProcessId();

        Assert.notNull(artProcessId,StringUtils.format("当前物料编号：{} 未绑定工艺！",materialNo));

        artProcess = artProcessService.selectUsableById(artProcessId);
        Assert.notNull(artProcess,StringUtils.format("当前物料编号：{} 绑定的工艺数据不存在或状态为不可用！",materialNo));

        processSteps = processStepService.selectAllMap().values().stream().
                filter(item -> item.getArtProcessId().equals(artProcessId)).collect(Collectors.toList());

        Assert.notEmpty(processSteps,StringUtils.format("当前物料编号：{} 绑定的工艺下的工序不存在或状态为不可用！",materialNo));

        processSteps.forEach(ProcessStepUtils::fillToTaskCmdIdList);

        Map<? extends Serializable, TopCentralTaskCmd> taskCmdMap = taskCmdService.selectAllMap();
        //将任务指令对象填充 至 工序中去
        processSteps.forEach(processStep -> {
            List<Integer> taskCmdIdList = processStep.getTaskCmdIdList();
            if(StringUtils.isNotEmpty(taskCmdIdList)){
                List<TopCentralTaskCmd> taskCmdList = new ArrayList<>(taskCmdIdList.size());
                taskCmdIdList.stream().filter(taskCmdMap::containsKey)
                        .forEach(taskCmdId->taskCmdList.add(taskCmdMap.get(taskCmdId)));
                processStep.setTaskCmdList(taskCmdList);
            }
        });
        //将工序集合填充至工艺中去
        artProcess.setProcessSteps(processSteps);
        //将工艺对象填充至物料中去
        material.setArtProcess(artProcess);
        return material;
    }


}
